/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo
*/

#include "simodo/variable/json/parser/JsonRdp.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/inout/token/FileStream.h"
#include "simodo/inout/format/fmt.h"

#include <fstream>
#include <algorithm>

namespace simodo::variable
{

namespace {

    class JsonAnalyzeDataBuilder_dummy : public JsonAnalyzeDataBuilder_interface
    {
    public:
        virtual void addGroup(const inout::Token & , const inout::Token & ) override {}
        virtual void addVariable(const inout::Token & ) override {}
        virtual void addConst(const inout::Token & ) override {}
    };

    static JsonAnalyzeDataBuilder_dummy dummy_builder;
}

JsonRdp::JsonRdp(inout::Reporter_abstract & m, const std::string json_file, Value & value) 
    : inout::RdpBaseSugar(m,_files) 
    , _builder(dummy_builder)
    , _json_file(json_file)
    , _value(value)
{
    _files.push_back(json_file);
}

JsonRdp::JsonRdp(inout::Reporter_abstract & m, JsonAnalyzeDataBuilder_interface & builder, const std::string json_file, Value & value) 
    : inout::RdpBaseSugar(m,_files) 
    , _builder(builder)
    , _json_file(json_file)
    , _value(value)
{
    _files.push_back(json_file);
}

bool JsonRdp::parse() 
{
    SIMODO_INOUT_STD_IFSTREAM(in, _json_file);

    if (!in) {
        reporter().reportFatal(inout::fmt("Ошибка при открытии файла '%1'").arg(_json_file));
        return false;
    }

    inout::InputStream stream(in);

    return parse(stream);
}

bool JsonRdp::parse(inout::InputStream_interface &stream) 
{
    inout::LexicalParameters lex;

    lex.markups = {
        {u"\"", u"\"", u"\\", inout::LexemeType::Annotation}
    };
    lex.masks = {
        {inout::BUILDING_NUMBER, inout::LexemeType::Number, 10}
    };
    lex.punctuation_chars = u"{}[],:";
    lex.punctuation_words = {u"true", u"false", u"null"};
    lex.national_alphabet  = u"";
    lex.id_extra_symbols   = u"_";
    lex.may_national_letters_use = false;
    lex.may_national_letters_mix = false;
    lex.is_case_sensitive  = true; // -V1048

    _tokenizer = std::make_unique<inout::Tokenizer>(0, stream, lex);

    inout::Token t = _tokenizer->getToken();

    if (t.type() == inout::LexemeType::Empty)
        return true;

    parseValue(t, _value);

    return _ok;
}

void JsonRdp::parseValue(const inout::Token & value_token, Value & value) const
{
    if (value_token.type() == inout::LexemeType::Punctuation)
    {
        if (value_token.lexeme() == u"{") {
            parseObject(value_token, value);
            return;
        }

        if (value_token.lexeme() == u"[") {
            parseArray(value_token, value);
            return;
        }

        if (value_token.lexeme() == u"true" || value_token.lexeme() == u"false")
        {
            value = Value(value_token.lexeme() == u"true");
            _builder.addConst(value_token);
            return;
        }

        if (value_token.lexeme() == u"null")
        {
            value = Value();
            _builder.addConst(value_token);
            return;
        }
    }
    else if (value_token.type() == inout::LexemeType::Annotation)
    {
        value = Value(value_token.lexeme());
        _builder.addConst(value_token);
        return;
    }
    else if (value_token.type() == inout::LexemeType::Number)
    {
        try
        {
            if (value_token.qualification() == inout::TokenQualification::RealNumber)
                value = Value(stod(inout::toU8(value_token.lexeme())));
            else
                value = Value(int64_t(stol(inout::toU8(value_token.lexeme()))));
        }
        catch(const std::exception & ex)
        {
            reporter().reportError(value_token.makeLocation(files()), inout::fmt("The numeric value is formatted incorrectly"));
        }
        _builder.addConst(value_token);
        return;
    }

    _ok = reportUnexpected(value_token, inout::fmt("number, string constant, '{', '[', 'true', 'false' or 'null'"));

    // 1. Встречено , ] } - пропущено значение
    if (value_token.lexeme() == u"," || value_token.lexeme() == u"}" || value_token.lexeme() == u"]") {
        reporter().reportInformation(inout::fmt("Пропущено значение"));
        value = Value();
        return;
    }

    // 2. Встречено : - ?
    reporter().reportInformation("?");
    value = Value();
}

void JsonRdp::parseObject(const inout::Token & open_brace_token, Value & value) const
{
    VariableSet_t member_list;

    inout::Token t = _tokenizer->getToken();

    if (t.type() == inout::LexemeType::Punctuation && t.lexeme() == u"}")
    {
        value = Value(member_list);
        _builder.addGroup(open_brace_token, t);
        return;
    }

    while(t.type() != inout::LexemeType::Empty)
    {
        std::u16string variable_name;
        bool colon = true;
        if (t.type() != inout::LexemeType::Annotation) {
            _ok = reportUnexpected(t, "member name (string)");
            if (t.lexeme() == u":") {
            }
            else {
                // Восстановление после ошибки методом "паники" до символов "," и "}"
                t = _tokenizer->getToken();
                while(t.type() != inout::LexemeType::Empty && t.lexeme() != u"," && t.lexeme() != u"}")
                    t = _tokenizer->getToken();
                colon = false;
                if (t.type() == inout::LexemeType::Empty)
                    return;
            }
        }
        else
        {
            auto it = find_if(member_list.begin(), member_list.end(),
                                [t](const Variable & var)
                                {
                                    return t.lexeme() == var.name();
                                });

            if (it != member_list.end())
            {
                reporter().reportError(t.makeLocation(files()), 
                                        inout::fmt("Member '%1' is duplicated").arg(t.lexeme()));
            }
        }

        if (colon) {
            _builder.addVariable(t);
            variable_name = t.lexeme();
            if (t.lexeme() != u":")
                t = _tokenizer->getToken();

            if (t.type() != inout::LexemeType::Punctuation || t.lexeme() != u":") {
                _ok = reportUnexpected(t, ":");
                // Восстановление после ошибки методом "паники" до символов "," и "}"
                t = _tokenizer->getToken();
                while(t.type() != inout::LexemeType::Empty && t.lexeme() != u"," && t.lexeme() != u"}")
                    t = _tokenizer->getToken();
                if (t.type() == inout::LexemeType::Empty)
                    return;
            }
            else {
                t = _tokenizer->getToken();

                Value variable_value;

                parseValue(t,variable_value);

                member_list.push_back(Variable {variable_name, variable_value});

                t = _tokenizer->getToken();
            }
        }

        if (t.type() == inout::LexemeType::Punctuation && t.lexeme() == u"}")
        {
            value = Value(member_list);
            _builder.addGroup(open_brace_token, t);
            return;
        }

        if (t.type() != inout::LexemeType::Punctuation || t.lexeme() != u",") {
            _ok = reportUnexpected(t, ",");
            // Восстановление после ошибки методом "паники" до символов СТРОКОВАЯ_КОНСТАНТА и "}"
            t = _tokenizer->getToken();
            while(t.type() != inout::LexemeType::Empty && t.type() != inout::LexemeType::Annotation && t.lexeme() != u"}")
                t = _tokenizer->getToken();
            if (t.type() == inout::LexemeType::Empty)
                return;
            if (t.type() == inout::LexemeType::Annotation)
                continue;
            value = Value(member_list);
            _builder.addGroup(open_brace_token, t);
            return;
        }

        t = _tokenizer->getToken();
    }
}

void JsonRdp::parseArray(const inout::Token & open_brace_token, Value & value) const
{
    std::vector<Value> value_list;

    inout::Token t = _tokenizer->getToken();

    if (t.type() == inout::LexemeType::Punctuation && t.lexeme() == u"]")
    {
        value = Value(value_list);
        _builder.addGroup(open_brace_token, t);
        return;
    }

    while(t.type() != inout::LexemeType::Empty)
    {
        Value array_element_value;

        parseValue(t,array_element_value);

        value_list.push_back(array_element_value);

        t = _tokenizer->getToken();

        if (t.type() == inout::LexemeType::Punctuation && t.lexeme() == u"]")
        {
            value = Value(value_list);
            _builder.addGroup(open_brace_token, t);
            return;
        }

        if (t.type() != inout::LexemeType::Punctuation || t.lexeme() != u",")
            break;

        t = _tokenizer->getToken();
    }

    _ok = reportUnexpected(t);
}

}